#include "ShadowMap.h"
#include "Texture.h"
#include "DirectionalLight.h"

#include "Shader.h"
#include "ShadowMapShader.h"

#include "glm\gtc\matrix_transform.hpp"

#include <iostream>
#include <map>
#include <string>

ShadowMap::ShadowMap()
{
	this->shader = nullptr;
	if(Shader::shaders.find("ShadowMapShader") != Shader::shaders.end())
	{
		this->shader = Shader::shaders.find("ShadowMapShader")->second;
	}
}

ShadowMap::~ShadowMap()
{

}

bool ShadowMap::Init(int windowWidth, int windowHeight, std::shared_ptr<DirectionalLight> light)
{
	this->light = light;

	biasMatrix = glm::mat4(0.5, 0.0, 0.0, 0.0,
							0.0, 0.5, 0.0, 0.0,
							0.0, 0.0, 0.5, 0.0,
							0.5, 0.5, 0.5, 1.0);

	//create fbo and texture
	glGenFramebuffers(1, &fbo);
	glGenTextures(1, &shadowMap);
	
	//setup texture
	glBindTexture(GL_TEXTURE_2D, shadowMap);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, windowWidth, windowHeight, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

	//bind texture to fbo
	glBindFramebuffer(GL_FRAMEBUFFER, fbo);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, shadowMap, 0);

	//set render buffers to 0 
	glDrawBuffer(GL_NONE);
	glReadBuffer(GL_NONE);

	//check fbo completeness
	GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
	if (status != GL_FRAMEBUFFER_COMPLETE) {
		printf("FB error, status: 0x%x\n", status);
		return false;
	}

	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	//setup a null texture and setup the texture id
	std::shared_ptr<Texture> newTexture = std::shared_ptr<Texture>(new Texture());
	Texture::textureMap.insert(std::pair<std::string, std::shared_ptr<Texture>>("ShadowMap", newTexture));
	newTexture->SetTextureID(shadowMap);

	return true;
}

void ShadowMap::PreUpdate()
{
	glm::vec3 forward = light->GetForward();
	glm::vec3 up = light->GetUp();
	glm::vec3 right = light->GetRight();
	glm::vec3 position = light->GetPosition();

	//camera matrix forward vector is -ve axis in left hand coordinate, so we flip it then inverse
	glm::mat4 matrix;
	matrix[0] = glm::vec4(right.x, right.y, right.z, 0.0);
	matrix[1] = glm::vec4(up.x, up.y, up.z, 0.0);
	matrix[2] = glm::vec4(-forward.x, -forward.y, -forward.z, 0.0);
	matrix[3] = glm::vec4(position.x, position.y, position.z, 1.0);

	viewMatrix = glm::inverse(matrix);	//final view matrix from light's POV
	orthoMatrix = glm::ortho<float>(-15, 15, -15, 15, -10, 20);

	VPMatrix = orthoMatrix * viewMatrix;
}

void ShadowMap::Bind()
{
	glBindFramebuffer(GL_FRAMEBUFFER, fbo);
	if(shader != nullptr)
	{
		shader->Bind();
	}
}

void ShadowMap::Unbind()
{
	if(shader != nullptr)
	{
		shader->Unbind();
	}
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
}